package emitter

import (
	"fmt"
	"mi/compiler/ast"
	"mi/compiler/binder"
	"mi/compiler/checker"
	"mi/compiler/diagnostic"
	"mi/compiler/token"
	"mi/compiler/utilities"
	"mi/internal/xpath"
	"os"
	"path/filepath"
	"strings"
)

type emitJava struct {
	writer     TextWriter
	sourceFile *ast.SourceFile
	resolver   checker.EmitResolver
	diagnostics  []*diagnostic.Diagnostic
}

func emitSourceFile(node *ast.SourceFile, emitter *emitter) []*diagnostic.Diagnostic {
	var e = new(emitJava)
	e.sourceFile = node
	e.resolver = emitter.resolver

	// Forward write symbol or type
	e.writer = newTextWriter()

	//emitter.compilerHost.GetNewLine(),
	//	func(symbol *binder.Symbol, enclosingDeclaration ast.Node, meaning binder.SymbolFlags) {
	//		emitter.resolver.WriteRealSymbol(symbol, enclosingDeclaration, meaning, e.writer)
	//	}, func(_type checker.Type, enclosingDeclaration ast.Node) {
	//	emitter.resolver.WriteRealType(_type, enclosingDeclaration, e.writer)
	//}

	// Emit nodes
	e.emitNode(node.PackageDeclaration)
	e.writer.WriteLine()
	e.emitLines(node.Imports)
	e.writer.WriteLine()
	e.emitLines(node.Declarations)

	// Resolve output dir and output file path.
	var pro = ast.GetProjectFileOfNode(node)
	var pkgPath = emitter.program.GetPackagePath(ast.GetPackageOfNode(node))
	var outDir = filepath.Join(filepath.Join(pro.OutputPath, "project", "src", "main", "java"),
		filepath.Join(strings.Split(pkgPath, ".")...))

	if diags := makeDirAll(outDir, os.ModePerm); diags != nil {
		e.diagnostics = append(e.diagnostics, diags...)
	} else {
		var baseName = xpath.GetBaseFileNameNoExtension(node.Uri.Filename())
		var outPath = filepath.Join(outDir, baseName+".java")
		e.diagnostics = append(e.diagnostics, writeFile(emitter.compilerHost, xpath.URIFromPath(outPath), e.writer.Bytes())...)
	}
	return e.diagnostics
}

func (e *emitJava) writeType(tpe checker.Type, enclosingDeclaration ast.Node) {
	var result = e.resolver.TypeToString(tpe, enclosingDeclaration, false)
	e.writer.WriteString(result)
}

func (e *emitJava) writeSymbol(symbol *binder.Symbol, enclosingDeclaration ast.Node, meaning binder.SymbolFlags) {
	var result = e.resolver.SymbolToString(symbol, enclosingDeclaration, meaning, false)
	e.writer.WriteString(result)
}

func (e *emitJava) getSourceTextOfLocalNode(node ast.Node) string {
	return utilities.GetSourceTextOfNodeFromSourceFile(e.sourceFile, node, false)
}

func (e *emitJava) emitLeadingComments(ast ast.Node) {

}

func (e *emitJava) emitTrailingComments(ast ast.Node) {

}

func (e *emitJava) emitOptional(prefix string, ast ast.Node) {
	if ast != nil {
		e.writer.WriteString(prefix)
		e.emitNode(ast)
	}
}

func (e *emitJava) emitToken(tok token.Token) {
	switch tok {
	case token.BreakKeyword:
		e.writer.WriteString(token.BreakKeywordOrg.ToString())
	case token.CaseKeyword:
		e.writer.WriteString(token.CaseKeywordOrg.ToString())
	case token.CatchKeyword:
		e.writer.WriteString(token.CatchKeywordOrg.ToString())
	case token.ClassKeyword:
		e.writer.WriteString(token.ClassKeywordOrg.ToString())
	case token.ConstKeyword:
		e.writer.WriteString(token.ConstKeywordOrg.ToString())
	case token.ContinueKeyword:
		e.writer.WriteString(token.ContinueKeywordOrg.ToString())
	case token.DefaultKeyword:
		e.writer.WriteString(token.DefaultKeywordOrg.ToString())
	case token.DoKeyword:
		e.writer.WriteString(token.DoKeywordOrg.ToString())
	case token.ElseKeyword:
		e.writer.WriteString(token.ElseKeywordOrg.ToString())
	case token.EnumKeyword:
		e.writer.WriteString(token.EnumKeywordOrg.ToString())
	case token.ExtendsKeyword:
		e.writer.WriteString(token.ExtendsKeywordOrg.ToString())
	case token.FalseKeyword:
		e.writer.WriteString(token.FalseKeywordOrg.ToString())
	case token.FinallyKeyword:
		e.writer.WriteString(token.FinallyKeywordOrg.ToString())
	case token.ForKeyword:
		e.writer.WriteString(token.ForKeywordOrg.ToString())
	case token.IfKeyword:
		e.writer.WriteString(token.IfKeywordOrg.ToString())
	case token.ImportKeyword:
		e.writer.WriteString(token.ImportKeywordOrg.ToString())
	case token.InstanceOfKeyword:
		e.writer.WriteString(token.InstanceOfKeywordOrg.ToString())
	case token.NullKeyword:
		e.writer.WriteString(token.NullKeywordOrg.ToString())
	case token.ReturnKeyword:
		e.writer.WriteString(token.ReturnKeywordOrg.ToString())
	case token.SuperKeyword:
		e.writer.WriteString(token.SuperKeywordOrg.ToString())
	case token.SwitchKeyword:
		e.writer.WriteString(token.SwitchKeywordOrg.ToString())
	case token.ThisKeyword:
		e.writer.WriteString(token.ThisKeywordOrg.ToString())
	case token.ThrowKeyword:
		e.writer.WriteString(token.ThrowKeywordOrg.ToString())
	case token.ThrowsKeyword:
		e.writer.WriteString(token.ThrowsKeywordOrg.ToString())
	case token.TrueKeyword:
		e.writer.WriteString(token.TrueKeywordOrg.ToString())
	case token.TryKeyword:
		e.writer.WriteString(token.TryKeywordOrg.ToString())
	case token.VarKeyword:
		e.writer.WriteString(token.VarKeywordOrg.ToString())
	case token.VoidKeyword:
		e.writer.WriteString(token.VoidKeywordOrg.ToString())
	case token.WhileKeyword:
		e.writer.WriteString(token.WhileKeywordOrg.ToString())
	case token.NewKeyword:
		e.writer.WriteString(token.NewKeywordOrg.ToString())
	case token.ImplementsKeyword:
		e.writer.WriteString(token.ImplementsKeywordOrg.ToString())
	case token.InterfaceKeyword:
		e.writer.WriteString(token.InterfaceKeywordOrg.ToString())
	case token.PackageKeyword:
		e.writer.WriteString(token.PackageKeywordOrg.ToString())
	case token.PrivateKeyword:
		e.writer.WriteString(token.PrivateKeywordOrg.ToString())
	case token.ProtectedKeyword:
		e.writer.WriteString(token.ProtectedKeywordOrg.ToString())
	case token.PublicKeyword:
		e.writer.WriteString(token.PublicKeywordOrg.ToString())
	case token.StaticKeyword:
		e.writer.WriteString(token.StaticKeywordOrg.ToString())
	case token.AbstractKeyword:
		e.writer.WriteString(token.AbstractKeywordOrg.ToString())
	case token.FinalKeyword:
		e.writer.WriteString(token.FinalKeywordOrg.ToString())
	case token.AsKeyword:
		e.writer.WriteString(token.AsKeywordOrg.ToString())
	case token.BooleanKeyword:
		e.writer.WriteString(token.BooleanKeywordOrg.ToString())
	case token.ConstructorKeyword:
		e.writer.WriteString(token.ConstructorKeywordOrg.ToString())
	case token.ShortKeyword:
		e.writer.WriteString(token.ShortKeywordOrg.ToString())
	case token.IntKeyword:
		e.writer.WriteString(token.IntKeywordOrg.ToString())
	case token.LongKeyword:
		e.writer.WriteString(token.LongKeywordOrg.ToString())
	case token.ByteKeyword:
		e.writer.WriteString(token.ByteKeywordOrg.ToString())
	case token.CharKeyword:
		e.writer.WriteString(token.CharKeywordOrg.ToString())
	case token.FloatKeyword:
		e.writer.WriteString(token.FloatKeywordOrg.ToString())
	case token.DoubleKeyword:
		e.writer.WriteString(token.DoubleKeywordOrg.ToString())
	default:
		e.writer.WriteString(tok.ToString())
	}
}

func (e *emitJava) emitList(nodes ast.List, split string) {
	if !isNil(nodes) {
		for i := 0; i < nodes.Len(); i++ {
			if i > 0 {
				e.writer.WriteString(split)
			}
			e.emitNode(nodes.NodeAt(i))
		}
	}
}

func (e *emitJava) emitLines(nodes ast.List) {
	if !isNil(nodes) {
		for i := 0; i < nodes.Len(); i ++ {
			e.emitNode(nodes.NodeAt(i))
			e.writer.WriteLine()
		}
	}
}

func (e *emitJava) emitTokenNode(node *ast.TokenNode) {
	e.emitToken(node.Token)
}

func (e *emitJava) emitModifier(node *ast.Modifier) {
	e.emitToken(node.Token)
}

func (e *emitJava) emitAnnotation(node *ast.Annotation) {
	e.writer.WriteString("@")
	e.emitNode(node.Type)
	if !isNil(node.Arguments) {
		e.writer.WriteString("(")
		e.emitList(node.Arguments, ", ")
		e.writer.WriteString(")")
	}
}

func (e *emitJava) emitQualifiedName(node *ast.QualifiedName) {
	e.emitNode(node.Left)
	e.writer.WriteString(".")
	e.emitNode(node.Right)
}

func (e *emitJava) emitHeritageClause(node *ast.HeritageClause) {
	e.emitToken(node.Token)
	e.writer.WriteString(" ")
	e.emitList(node.Types, ", ")
}

func (e *emitJava) emitCaseClause(node *ast.CaseClause) {
	e.emitNode(node.Token)
	e.emitOptional(" ", node.Expression)
	e.writer.WriteString(":")
	e.writer.IncreaseIndent()
	e.emitLines(node.Statements)
	e.writer.DecreaseIndent()
}

func (e *emitJava) emitCaseBlock(node *ast.CaseBlock) {
	e.emitToken(token.OpenBrace)
	e.writer.IncreaseIndent()
	e.emitLines(node.Clauses)
	e.writer.DecreaseIndent()
	e.writer.WriteLine()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitCatchClause(node *ast.CatchClause) {
	e.emitToken(token.CatchKeyword)
	e.emitToken(token.OpenParen)
	e.emitNode(node.VariableDeclaration)
	e.emitToken(token.CloseParen)
	e.emitNode(node.Block)
}

func (e *emitJava) emitPackageDeclaration(node *ast.PackageDeclaration) {
	e.emitToken(token.PackageKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	e.writer.WriteString(";")
}

func (e *emitJava) emitImportDeclaration(node *ast.ImportDeclaration) {
	e.emitToken(token.ImportKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Path)
	e.writer.WriteString(".")
	e.emitNode(node.Target)
	e.writer.WriteString(";")
}

func (e *emitJava) inForOfInitializer(node ast.Node) bool {
	switch pn := node.Parent().(type) {
	case *ast.ForOfStatement:
		return pn.VariableDeclaration == node
	}
	return false
}

func (e *emitJava) emitVariableDeclaration(node *ast.VariableDeclaration) {
	e.emitLeadingComments(node)
	if isNil(node.Type) {
		var symbol = binder.GetSymbolOfNode(node)
		var tpe = e.resolver.GetTypeOfSymbol(symbol)
		e.writeType(tpe, node)
	} else {
		e.emitNode(node.Type)
	}

	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	e.emitOptional(" = ", node.Initializer)
}

func (e *emitJava) emitParameterDeclaration(node *ast.ParameterDeclaration) {
	e.emitNode(node.Type)
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
}

func (e *emitJava) emitTypeParameterDeclaration(node *ast.TypeParameterDeclaration) {
	e.emitNode(node.Name)
	if !isNil(node.Constraint) {
		e.emitToken(token.ExtendsKeyword)
		e.writer.WriteString(" ")
		e.emitNode(node.Constraint)
	}
}

func (e *emitJava) emitArrowFunctionDeclaration(node *ast.ArrowFunctionDeclaration) {
	e.emitToken(token.OpenParen)
	e.emitList(node.TypeParameters, ", ")
	e.emitToken(token.CloseParen)
	e.writer.WriteString(" -> ")
	e.emitNode(node.Body)
}

func (e *emitJava) emitConstructorDeclaration(node *ast.ConstructorDeclaration) {
	var name = ast.GetDeclarationName(node.Parent().(ast.Declaration))
	e.emitNode(name)
	e.emitToken(token.OpenParen)
	e.emitList(node.Parameters, ", ")
	e.emitToken(token.CloseParen)
	if node.Throws != nil {
		e.writer.WriteString(" ")
		e.emitToken(token.ThrowsKeyword)
		e.writer.WriteString(" ")
		e.emitList(node.Throws, ", ")
		e.writer.WriteString(" ")
	}
	e.emitNode(node.Body)
}

func (e *emitJava) emitFieldDeclaration(node *ast.FieldDeclaration) {
	e.emitLeadingComments(node)
	if isNil(node.Type) {
		var symbol = binder.GetSymbolOfNode(node)
		var tpe = e.resolver.GetTypeOfSymbol(symbol)
		e.writeType(tpe, node)
	} else {
		e.emitNode(node.Type)
	}

	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	e.emitOptional(" = ", node.Initializer)
	e.writer.WriteString(";")
}

func (e *emitJava) emitMethodDeclaration(node *ast.MethodDeclaration) {
	if !isNil(node.TypeParameters) {
		e.emitToken(token.LessThan)
		e.emitList(node.TypeParameters, ", ")
		e.emitToken(token.GreaterThan)
		e.writer.WriteString(" ")
	}
	if isNil(node.Type) {
		e.writer.WriteString("void")
	} else {
		e.emitNode(node.Type)
	}
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	e.emitToken(token.OpenParen)
	e.emitList(node.Parameters, ", ")
	e.emitToken(token.CloseParen)
	if node.Throws != nil {
		e.writer.WriteString(" ")
		e.emitToken(token.ThrowsKeyword)
		e.writer.WriteString(" ")
		e.emitList(node.Throws, ", ")
		e.writer.WriteString(" ")
	}

	if !isNil(node.Default) {
		e.writer.WriteString(" ")
		e.emitToken(token.DefaultKeyword)
		e.writer.WriteString(" ")
		e.emitNode(node.Value)
	}

	if isNil(node.Body) {
		e.writer.WriteString(";")
	} else {
		e.writer.WriteString(" ")
		e.emitNode(node.Body)
	}
}

func (e *emitJava) emitClassDeclaration(node *ast.ClassDeclaration) {
	e.emitToken(token.ClassKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	if !isNil(node.TypeParameters) {
		e.emitToken(token.LessThan)
		e.emitList(node.TypeParameters, ", ")
		e.emitToken(token.GreaterThan)
	}
	e.writer.WriteString(" ")
	e.emitList(node.HeritageClauses, " ")
	e.emitToken(token.OpenBrace)
	e.writer.WriteLine()
	e.writer.IncreaseIndent()
	e.emitLines(node.Members)
	e.writer.DecreaseIndent()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitAnonymousClassDeclaration(node *ast.BodyDeclaration) {
	e.emitToken(token.OpenBrace)
	e.writer.WriteLine()
	e.writer.IncreaseIndent()
	e.emitLines(node.Members)
	e.writer.DecreaseIndent()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitEnumItemDeclaration(node *ast.EnumItemDeclaration) {
	e.emitNode(node.Name)
	if !isNil(node.Arguments) {
		e.emitToken(token.OpenParen)
		e.emitList(node.Arguments, ", ")
		e.emitToken(token.CloseParen)
	}
}

func (e *emitJava) emitEnumDeclaration(node *ast.EnumDeclaration) {
	e.emitToken(token.EnumKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	e.emitLines(node.HeritageClauses)
	e.emitToken(token.OpenBrace)
	e.emitList(node.EnumItems, ", ")
	e.writer.WriteString(";")
	e.writer.IncreaseIndent()
	e.emitLines(node.Members)
	e.writer.DecreaseIndent()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitInterfaceDeclaration(node *ast.InterfaceDeclaration) {
	e.emitToken(token.InterfaceKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	if !isNil(node.TypeParameters) {
		e.emitToken(token.LessThan)
		e.emitList(node.TypeParameters, ", ")
		e.emitToken(token.GreaterThan)
	}
	if node.HeritageClauses != nil {
		e.writer.WriteString(" ")
		e.emitLines(node.HeritageClauses)
	}
	e.writer.WriteString(" ")
	e.emitToken(token.OpenBrace)
	e.writer.WriteLine()
	e.writer.IncreaseIndent()
	e.emitLines(node.Members)
	e.writer.DecreaseIndent()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitAnnotationDeclaration(node *ast.AnnotationDeclaration) {
	e.emitToken(token.At)
	e.emitToken(token.InterfaceKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Name)
	e.writer.WriteString(" ")
	e.emitToken(token.OpenBrace)
	e.writer.WriteLine()
	e.writer.IncreaseIndent()
	e.emitLines(node.Members)
	e.writer.DecreaseIndent()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitIdentifier(node *ast.Identifier) {
	var symbol *binder.Symbol
	if ast.IsRightSideOfQualifiedNameOrSelectorExpression(node) {
		symbol = e.resolver.GetResolvedSymbol(node.Parent())
	} else {
		symbol = e.resolver.GetResolvedSymbol(node)
	}

	if symbol != nil {
		var realName = e.resolver.GetRealSymbolName(symbol)
		e.writer.WriteString(realName)
	} else {
		e.writer.WriteString(node.Value)
	}
}

func (e *emitJava) emitArrowExpression(node *ast.ArrowExpression) {
	e.emitNode(node.Declaration)
}

func (e *emitJava) emitPrefixUnaryExpression(node *ast.PrefixUnaryExpression) {
	e.emitNode(node.Operator)
	e.emitNode(node.Operand)
}

func (e *emitJava) emitPostfixUnaryExpression(node *ast.PostfixUnaryExpression) {
	e.emitNode(node.Operand)
	e.emitNode(node.Operator)
}

func (e *emitJava) emitBinaryExpression(node *ast.BinaryExpression) {
	e.emitNode(node.Left)
	e.writer.WriteString(" ")
	e.emitNode(node.Operator)
	e.writer.WriteString(" ")
	e.emitNode(node.Right)
}

func (e *emitJava) emitConditionalExpression(node *ast.ConditionalExpression) {
	e.emitNode(node.Condition)
	e.writer.WriteString(" ? ")
	e.emitNode(node.WhenTrue)
	e.writer.WriteString(" : ")
	e.emitNode(node.WhenFalse)
}

func (e *emitJava) emitThisExpression(node *ast.ThisExpression) {
	e.emitToken(token.ThisKeyword)
	if !isNil(node.Expression) {
		e.writer.WriteString(".")
		e.emitNode(node.Expression)
	}
}

func (e *emitJava) emitSuperExpression(node *ast.SuperExpression) {
	e.emitToken(token.SuperKeyword)
	if !isNil(node.Expression) {
		e.writer.WriteString(".")
		e.emitNode(node.Expression)
	}
}

func (e *emitJava) emitQualifiedExpression(node *ast.QualifiedExpression) {
	e.emitToken(token.NewKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Type)
	e.writer.WriteString("()")
}

func (e *emitJava) emitConstructCallExpression(node *ast.ConstructorExpression) {
	if !isNil(node.Scope) {
		e.emitNode(node.Scope)
		e.writer.WriteString(".")
	}
	e.emitToken(token.NewKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.TypeName)

	var signature, _ = e.resolver.GetResolvedSignature(node)
	if signature.Target != nil && len(signature.Target.TypeParameters) > 0 {
		e.emitToken(token.LessThan)
		var mapper = signature.Mapper
		for i, parameter := range signature.Target.TypeParameters {
			if i > 0 {
				e.writer.WriteString(", ")
			}
			var tpe = mapper.Invoke(parameter)
			e.writeType(tpe, node)
		}
		e.emitToken(token.GreaterThan)
	}

	if len(signature.TypeParameters) > 0 {
		e.emitToken(token.LessThan)
		for i, parameter := range signature.TypeParameters {
			if i > 0 {
				e.writer.WriteString(", ")
			}
			e.writeType(parameter, node)
		}
		e.emitToken(token.GreaterThan)
	}

	e.emitToken(token.OpenParen)
	e.emitList(node.Arguments, ", ")
	e.emitToken(token.CloseParen)
}

func (e *emitJava) emitNewObjectExpression(node *ast.NewObjectExpression) {
	e.emitNode(node.Type)
	e.emitNode(node.Body)
}

func (e *emitJava) emitNewNewArrayExpression(node *ast.NewArrayExpression) {
	e.emitToken(token.NewKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Type)
	e.emitNode(node.Initializer)
}

func (e *emitJava) emitAsExpression(node *ast.AsExpression) {
	e.emitToken(token.OpenParen)
	e.emitNode(node.Type)
	e.emitToken(token.CloseParen)
	e.emitNode(node.Expression)
}

func (e *emitJava) emitArrayLiteralExpression(node *ast.ArrayLiteralExpression) {
	e.emitToken(token.OpenBrace)
	e.emitList(node.Elements, ", ")
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitParenthesizedExpression(node *ast.ParenthesizedExpression) {
	e.emitToken(token.OpenParen)
	e.emitNode(node.Expression)
	e.emitToken(token.CloseParen)
}

func (e *emitJava) emitElementAccessExpression(node *ast.ElementAccessExpression) {
	e.emitNode(node.Expression)
	e.writer.WriteString("[")
	e.emitNode(node.ArgumentExpression)
	e.writer.WriteString("]")
}

func (e *emitJava) emitLiteralExpression(node *ast.LiteralExpression) {
	switch node.Token {
	case token.TrueKeyword:
		e.emitToken(token.TrueKeyword)
	case token.FalseKeyword:
		e.emitToken(token.FalseKeyword)
	case token.NullKeyword:
		e.emitToken(token.NullKeyword)
	default:
		e.writer.WriteString(e.getSourceTextOfLocalNode(node))
	}
}

func (e *emitJava) emitClassExpression(node *ast.ClassExpression) {
	var tpe = e.resolver.GetTypeFromNode(node.Type)
	e.writer.WriteString(e.resolver.TypeToString(tpe, node, false))

	e.writer.WriteString(".")
	e.emitToken(token.ClassKeyword)
}

func (e *emitJava) emitSelectorExpression(node *ast.SelectorExpression) {
	e.emitNode(node.Expression)
	e.writer.WriteString(".")
	e.emitNode(node.Name)
}

func (e *emitJava) emitCallExpression(node *ast.CallExpression) {
	var scope ast.Node
	var expr ast.Node
	switch n := node.Expression.(type) {
	case *ast.SelectorExpression:
		scope = n.Expression
		expr = n.Name
	default:
		expr = n
	}

	if !isNil(scope) {
		e.emitNode(scope)
		e.writer.WriteString(".")
	}

	var signature, _ = e.resolver.GetResolvedSignature(node)
	if signature.Target != nil && len(signature.Target.TypeParameters) > 0 {
		e.emitToken(token.LessThan)
		var mapper = signature.Mapper
		for i, parameter := range signature.Target.TypeParameters {
			if i > 0 {
				e.writer.WriteString(", ")
			}
			var tpe = mapper.Invoke(parameter)
			e.writeType(tpe, node)
		}
		e.emitToken(token.GreaterThan)
	}

	e.emitNode(expr)

	e.emitToken(token.OpenParen)
	e.emitList(node.Arguments, ", ")
	e.emitToken(token.CloseParen)
}

func (e *emitJava) emitEmptyStatement(node *ast.EmptyStatement) {
}

func (e *emitJava) emitDeclarationStatement(node *ast.DeclarationStatement) {
	e.emitNode(node.Declaration)
	e.writer.WriteString(";")
}

func (e *emitJava) emitExpressionStatement(node *ast.ExpressionStatement) {
	e.emitNode(node.Expression)
	e.writer.WriteString(";")
}

func (e *emitJava) emitBlockStatement(node *ast.BlockStatement) {
	e.emitToken(token.OpenBrace)
	e.writer.WriteLine()
	e.writer.IncreaseIndent()
	e.emitLines(node.Statements)
	e.writer.DecreaseIndent()
	e.emitToken(token.CloseBrace)
}

func (e *emitJava) emitTryStatement(node *ast.TryStatement) {
	e.emitToken(token.TryKeyword)
	e.emitNode(node.TryBlock)
	e.emitList(node.CatchClauses, "")
	e.emitNode(node.FinallyBlock)
}

func (e *emitJava) emitIfStatement(node *ast.IfStatement) {
	e.emitToken(token.IfKeyword)
	e.emitToken(token.OpenParen)
	e.emitNode(node.Condition)
	e.emitToken(token.CloseParen)
	e.emitNode(node.ThenStatement)
	if !isNil(node.ElseStatement) {
		e.writer.WriteLine()
		e.emitToken(token.ElseKeyword)
		e.writer.WriteString(" ")
		e.emitNode(node.ElseStatement)
	}
}

func (e *emitJava) emitDoStatement(node *ast.DoStatement) {
	e.emitToken(token.DoKeyword)
	e.emitNode(node.Body)
	e.emitToken(token.WhileKeyword)
	e.emitToken(token.OpenParen)
	e.emitNode(node.Condition)
	e.emitToken(token.CloseParen)
	e.writer.WriteString(";")
}

func (e *emitJava) emitWhileStatement(node *ast.WhileStatement) {
	e.emitToken(token.WhileKeyword)
	e.emitToken(token.OpenParen)
	e.emitNode(node.Condition)
	e.emitToken(token.CloseParen)
	e.emitNode(node.Body)
}

func (e *emitJava) emitForStatement(node *ast.ForStatement) {
	e.emitToken(token.ForKeyword)
	e.emitToken(token.OpenParen)
	e.emitOptional("", node.Initializer)
	e.writer.WriteString(";")
	e.emitOptional(" ", node.Condition)
	e.writer.WriteString(";")
	e.emitOptional(" ", node.Incrementor)
	e.emitToken(token.CloseParen)
	e.emitNode(node.Body)
}

func (e *emitJava) emitForOfStatement(node *ast.ForOfStatement) {
	e.emitToken(token.ForKeyword)
	e.emitToken(token.OpenParen)
	e.emitNode(node.VariableDeclaration)
	e.writer.WriteString(" : ")
	e.emitNode(node.Expression)
	e.emitToken(token.CloseParen)
	e.emitNode(node.Body)
}

func (e *emitJava) emitBranchStatement(node *ast.BranchStatement) {
	e.emitToken(node.Token)
	e.writer.WriteString(" ")
	e.emitNode(node.Label)
	e.writer.WriteString(";")
}

func (e *emitJava) emitThrowStatement(node *ast.ThrowStatement) {
	e.emitToken(token.ThrowKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Expression)
	e.writer.WriteString(";")
}

func (e *emitJava) emitReturnStatement(node *ast.ReturnStatement) {
	e.emitToken(token.ReturnKeyword)
	e.writer.WriteString(" ")
	e.emitNode(node.Expression)
	e.writer.WriteString(";")
}

func (e *emitJava) emitSwitchStatement(node *ast.SwitchStatement) {
	e.emitToken(token.SwitchKeyword)
	e.writer.WriteString(" ")
	e.emitToken(token.OpenParen)
	e.emitNode(node.Expression)
	e.emitToken(token.CloseParen)
	e.emitNode(node.Body)
}

func (e *emitJava) emitLabeledStatement(node *ast.LabeledStatement) {
	e.emitNode(node.Label)
	e.writer.WriteString(": ")
	e.emitNode(node.Statement)
}

func (e *emitJava) emitBoundsType(node *ast.BoundsType) {
	e.writer.WriteString("?")
	if !isNil(node.Bound) {
		e.writer.WriteString(" ")
		e.emitNode(node.Bound)
		e.writer.WriteString(" ")
		e.emitNode(node.Type)
	}
}

func (e *emitJava) emitIntrinsicType(node *ast.IntrinsicType) {
	e.emitToken(node.Token)
}

func (e *emitJava) emitTypeReference(node *ast.TypeReference) {
	e.emitNode(node.Name)
	if !isNil(node.TypeArguments) {
		e.emitToken(token.LessThan)
		e.emitList(node.TypeArguments, ", ")
		e.emitToken(token.GreaterThan)
	}
}

func (e *emitJava) emitArrayType(node *ast.ArrayType) {
	e.emitNode(node.Element)
	e.writer.WriteString("[")
	e.emitNode(node.Dimension)
	e.writer.WriteString("]")
}

func (e *emitJava) emitNode(node ast.Node) {
	if isNil(node) {
		return
	}

	if node.GetAnnotations() != nil {
		e.emitLines(node.GetAnnotations())
	}
	if node.GetModifiers() != nil {
		e.emitList(node.GetModifiers(), " ")
		e.writer.WriteString(" ")
	}

	switch n := node.(type) {
	case *ast.TokenNode:
		e.emitTokenNode(n)
	case *ast.Modifier:
		e.emitModifier(n)
	case *ast.Annotation:
		e.emitAnnotation(n)
	case *ast.QualifiedName:
		e.emitQualifiedName(n)
	case *ast.HeritageClause:
		e.emitHeritageClause(n)
	case *ast.CaseClause:
		e.emitCaseClause(n)
	case *ast.CatchClause:
		e.emitCatchClause(n)
	case *ast.CaseBlock:
		e.emitCaseBlock(n)
	case *ast.PackageDeclaration:
		e.emitPackageDeclaration(n)
	case *ast.ImportDeclaration:
		e.emitImportDeclaration(n)
	case *ast.VariableDeclaration:
		e.emitVariableDeclaration(n)
	case *ast.ParameterDeclaration:
		e.emitParameterDeclaration(n)
	case *ast.TypeParameterDeclaration:
		e.emitTypeParameterDeclaration(n)
	case *ast.ArrowFunctionDeclaration:
		e.emitArrowFunctionDeclaration(n)
	case *ast.ConstructorDeclaration:
		e.emitConstructorDeclaration(n)
	case *ast.FieldDeclaration:
		e.emitFieldDeclaration(n)
	case *ast.MethodDeclaration:
		e.emitMethodDeclaration(n)
	case *ast.ClassDeclaration:
		e.emitClassDeclaration(n)
	case *ast.BodyDeclaration:
		e.emitAnonymousClassDeclaration(n)
	case *ast.EnumItemDeclaration:
		e.emitEnumItemDeclaration(n)
	case *ast.EnumDeclaration:
		e.emitEnumDeclaration(n)
	case *ast.InterfaceDeclaration:
		e.emitInterfaceDeclaration(n)
	case *ast.AnnotationDeclaration:
		e.emitAnnotationDeclaration(n)
	case *ast.Identifier:
		e.emitIdentifier(n)
	case *ast.ArrowExpression:
		e.emitArrowExpression(n)
	case *ast.PrefixUnaryExpression:
		e.emitPrefixUnaryExpression(n)
	case *ast.PostfixUnaryExpression:
		e.emitPostfixUnaryExpression(n)
	case *ast.BinaryExpression:
		e.emitBinaryExpression(n)
	case *ast.ConditionalExpression:
		e.emitConditionalExpression(n)
	case *ast.ThisExpression:
		e.emitThisExpression(n)
	case *ast.SuperExpression:
		e.emitSuperExpression(n)
	case *ast.QualifiedExpression:
		e.emitQualifiedExpression(n)
	case *ast.ConstructorExpression:
		e.emitConstructCallExpression(n)
	case *ast.NewObjectExpression:
		e.emitNewObjectExpression(n)
	case *ast.NewArrayExpression:
		e.emitNewNewArrayExpression(n)
	case *ast.AsExpression:
		e.emitAsExpression(n)
	case *ast.ArrayLiteralExpression:
		e.emitArrayLiteralExpression(n)
	case *ast.ParenthesizedExpression:
		e.emitParenthesizedExpression(n)
	case *ast.ElementAccessExpression:
		e.emitElementAccessExpression(n)
	case *ast.LiteralExpression:
		e.emitLiteralExpression(n)
	case *ast.ClassExpression:
		e.emitClassExpression(n)
	case *ast.SelectorExpression:
		e.emitSelectorExpression(n)
	case *ast.CallExpression:
		e.emitCallExpression(n)
	case *ast.EmptyStatement:
		e.emitEmptyStatement(n)
	case *ast.DeclarationStatement:
		e.emitDeclarationStatement(n)
	case *ast.ExpressionStatement:
		e.emitExpressionStatement(n)
	case *ast.BlockStatement:
		e.emitBlockStatement(n)
	case *ast.TryStatement:
		e.emitTryStatement(n)
	case *ast.IfStatement:
		e.emitIfStatement(n)
	case *ast.DoStatement:
		e.emitDoStatement(n)
	case *ast.WhileStatement:
		e.emitWhileStatement(n)
	case *ast.ForStatement:
		e.emitForStatement(n)
	case *ast.ForOfStatement:
		e.emitForOfStatement(n)
	case *ast.BranchStatement:
		e.emitBranchStatement(n)
	case *ast.ThrowStatement:
		e.emitThrowStatement(n)
	case *ast.ReturnStatement:
		e.emitReturnStatement(n)
	case *ast.SwitchStatement:
		e.emitSwitchStatement(n)
	case *ast.LabeledStatement:
		e.emitLabeledStatement(n)
	case *ast.BoundsType:
		e.emitBoundsType(n)
	case *ast.IntrinsicType:
		e.emitIntrinsicType(n)
	case *ast.TypeReference:
		e.emitTypeReference(n)
	case *ast.ArrayType:
		e.emitArrayType(n)
	default:
		panic(fmt.Sprintf("Unknown node kind %T in 'emitNode'", node))
	}
}